package gu.sql2java;

import java.util.LinkedList;

import com.google.common.collect.ImmutableList;

import gu.sql2java.exception.RuntimeDaoException;

import static com.google.common.base.Preconditions.*;
import static gu.sql2java.SimpleLog.*;

/** 
 * container for multiple listener management
 * @author guyadong 
 */
class ListenerContainerLocal <B>extends ListenerContainer<B> {

	static final TransactionListenerImpl TRANSACTION_LISTENER = new TransactionListenerImpl();
	public ListenerContainerLocal(FireType fireType) {
		super(BaseTableManager.isDebug(), fireType);
	}

	@Override
	public void afterInsert(final B bean)throws RuntimeDaoException{
		if(isLocalReqFirer()){
			synchronized (listeners) {
				for(final TableListener<B> listener:listeners){
					TRANSACTION_LISTENER.runCommitTask(new Runnable() {

						@Override
						public void run() {
							try{
								listener.afterInsert(bean);
							}catch(Exception e){
								log("afterInsert listener %s error:%s",listener.getClass().getName(),e.getMessage());
								if(trace){
									log(e);
								}
							}
						}
					});
				}
			}
		}else {
			super.afterInsert(bean);
		}
	}

	@Override
	public void afterUpdate(final B bean)throws RuntimeDaoException{
		if(isLocalReqFirer()){
			synchronized (listeners) {
				for(final TableListener<B> listener:listeners){
					TRANSACTION_LISTENER.runCommitTask(new Runnable() {

						@Override
						public void run() {
							try{
								listener.afterUpdate(bean);
							}catch(Exception e){
								log("afterUpdate listener %s error:%s",listener.getClass().getName(),e.getMessage());
								if(trace){
									log(e);
								}
							}
						}
					});
				}
			}
		}else {
			super.afterUpdate(bean);
		}
	}

	@Override
	public void afterDelete(final B bean)throws RuntimeDaoException{
		if(isLocalReqFirer()){
			synchronized (listeners) {
				for(final TableListener<B> listener:listeners){
					TRANSACTION_LISTENER.runCommitTask(new Runnable() {

						@Override
						public void run() {
							try{
								listener.afterDelete(bean);
							}catch(Exception e){
								log("afterDelete listener %s error:%s",listener.getClass().getName(),e.getMessage());
								if(trace){
									log(e);
								}
							}
						}
					});
				}
			}
		}else {
			super.afterDelete(bean);
		}
	}

	@Override
	public void done()throws RuntimeDaoException{
		if(isLocalReqFirer()){
			synchronized (listeners) {
				for(final TableListener<B> listener:listeners){
					TRANSACTION_LISTENER.runDoneTask(new Runnable() {

						@Override
						public void run() {
							try{
								listener.done();
							}catch(Exception e){
								log("done listener %s error:%s",listener.getClass().getName(),e.getMessage());
								if(trace){
									log(e);
								}
							}
						}
					});

				}
			}
		}
	}
	private static class TransactionListenerImpl implements TransactionListener{
		private static final ThreadLocal<LinkedList<Runnable>> commitTasks = new ThreadLocal<>();
		private static final ThreadLocal<LinkedList<Runnable>> doneTasks = new ThreadLocal<>();
		@Override
		public void onBegin() {
			commitTasks.set(new LinkedList<Runnable>());
			doneTasks.set(new LinkedList<Runnable>());
		}
		
		private static void runEachTask(LinkedList<Runnable> tasks){
			try {
				// task.run()执行时可能会修改 tasks 的内容,如果直接对tasks遍历会导致 ConcurrentModificationException 异常 
				// 所以这里在tasks的复本(copy)上执行遍历
				ImmutableList<Runnable> copy;
				while (! tasks.isEmpty()) {
					copy = ImmutableList.copyOf(tasks);
					for (Runnable task : copy) {
						task.run();
					}
					tasks.removeAll(copy);
				} 
			} catch (Exception e) {
				log("{}:{}",e.getClass().getName(),e.getMessage());
			}
		}
	
		
		@Override
		public void onCommit() {
			// run all commit tasks
			runEachTask(checkNotNull(commitTasks.get(),"'onBegin' must be called firstly"));
		}
		@Override
		public void onEnd() {
			// run all done tasks
			runEachTask(checkNotNull(doneTasks.get(),"'onBegin' must be called firstly"));
			commitTasks.remove();
			doneTasks.remove();
		}
		void runCommitTask(Runnable task){
			LinkedList<Runnable> tasks = commitTasks.get();
			if(tasks != null){
				tasks.add(task);
			}else {
				task.run();
			}
		}
		void runDoneTask(Runnable task){
			LinkedList<Runnable> tasks = doneTasks.get();
			if(tasks != null){
				tasks.add(task);
			}else {
				task.run();
			}
		}
	}
}